\chapter{Section I: 到底是怎么回事?}
\section{Part I: 古怪的Haskell}

首先，Haskell没有更新操作符(update operator)。如果你不明白这句话的含义，那么请继续读下去，因为这个教程是为你而编写的。对于'更新操作符'，我的意思是在正规Haskell中以下情况不会发生：

\begin{verbatim}
int a
a := 4
print a
a := 5
print a

> 4
> 5
\end{verbatim}

上面的程序样式，即'定义一个变量，把数据放在其中，使用它，然后替换其中的数据，再使用'，在正规Haskell中不会发生。对于这个概念，曾经使用过LISP或Scheme的读者不会陌生，不过我敢肯定其他读者可能会陷入困惑。下面是Haskell的工作方式，用伪码表示：

\begin{verbatim}
print a
    
int a
a = 5
    
> 5
or
int a
a = 5
    
print a
    
> 5
\end{verbatim}

这些操作的次序无关紧要。关于第一个例子使用':='而第二个例子使用'='还有一个原因，在'命令式'语言中，存储数据是一个操作，且在一个操作序列中执行；在类似Haskell的函数式语言中，等号意思是'正是..'。换句话说，每个变量与其值相等事实上不仅仅在操作序列执行赋值语句后是如此，而且在执行过程的任何时刻都是如此。

现在，你们中间的一些人会说：“很不错，Eric，不过一门到处都是硬编码的语言的好处究竟是什么呢？我是不是需要在定义时为所有变量都赋上正确的值？Isn't 'computing' values the whole point of a 'computer'？” 你们是正确的；提前知道结果让计算看起来很奇怪。Haskell能作为补偿的是你不必为返回结果而存储数据。

我将在本教程中设置一些短暂的停顿，因为学习Haskell是很痛苦的，至少对我而言是这样的。我需要休息。并且每当我尝试去理解时，我的大
脑就很受伤。不过无论如何，让我们再瞧瞧这些语句吧：你不必为返回一个结果而存储数据。下面是一个C语言函数的例子：

\begin{verbatim}
int foo (int bar) {
    int result;
    result = bar * 10 + 4;
    return result;
}
\end{verbatim}

现在，重要的部分是函数中间的那个表达式，它也可以这样来写：

\begin{verbatim}
int foo (int bar) {
    return bar * 10 + 4;
}
\end{verbatim}

这两种实现方式是一样的，但是第二种方式更为短小，也更为清晰。有了这样的函数，你就可以这样说了：“foo(x)的值等于(x * 10 + 4)”或更简单的说“foo(x) = x * 10 + 4”。我知道你在说：“大多函数不是这么简单的”。没错，不过请先耐心等待一下。Haskell拥有比其他大多数语言更为强大的编写函数的方法，并且很多复杂的操作在Haskell中都会看起来很简单。使用这些方法的关键在于将你的思维方式从'定义数据，然后改变它'转变为'定义带返回值的函数，然后将这个函数应用于所有输入参数'。

\section{Part II: 输入和输出}

稍后我们会回到这个思路上。Haskell与C/C++是如此的不同，许多概念只有在与其他概念结合在一起时才有意义。在全面详细解释每一个概念之前，我需要介绍一些概念的基本知识。

我们继续，现在每个人头脑中的问题可能是“I/O是如何工作的？”或是“这些方法到底是什么？” IO是Haskell中最复杂的部分之一，稍后我将描述它是如何工作的以及a setup for programming with GHC。在那之前，我们使用GHCi或Hugs来尝试这些例子。它们拥有一个交互提示符，供你输入表达式，就如同一个函数在接收参数。然后它们打印求值后的结果。当使用它们时，诸如'a = 4'这样的变量绑定也可输入，所以这里的例子都应该正常工作。如果要写自己的函数，你需要编辑一个Haskell源文件并首先加载它。使用GHC需要知道一些Haskell中更为复杂的方法，所以我们到那时再介绍它。

为了你自己的函数使用Hugs和GHCi，你需要编辑一个Haskell源文件，并且将其加载到解释程序中，一般来说，这个工作步骤如下：

\begin{enumerate}
\item 打开一个文本编辑器，编写Haskell源代码。
\item 将代码保存在一个扩展名为.hs的文件中，例如：'test.hs'。
\item 在源码文件所在的当前目录下，运行Hugs或GHCi。
\item 在Hugs或GHCi中，在命令提示符下敲入':l test.hs'。用的是小写字母'l'。
\item 需要模块(modules)的源码，比如说Data.Maybe，应该在文件顶部包含'import Data.Maybe'。
\end{enumerate}

注意模块'Prelude'是自动导入的。这个模块包含了语言的大部分基本元素。

\section{Part III: 类型的基本介绍}

再次上路，让我讨论一下那些方法吧。Haskell的最大优势在于提供给程序员们一种简单且清晰地定义一些非常有用函数的能力。我们再回顾一下之前用C语言实现的例子：

\begin{verbatim}
int foo (int bar) {
    return bar * 10 + 4;
}
\end{verbatim}

在Haskell中，要实现foo这个函数，你可以这样来写：

\begin{verbatim}
foo bar = bar * 10 + 4
\end{verbatim}

就是这些，除了其类型：

\begin{verbatim}
foo :: Int -> Int
\end{verbatim}

这个类型声明读作，“foo具有从Int到Int的类型”，意思是它接受一个Int型参数并且返回一个Int型结果。将两者放在一起，你可以这样写：

\begin{verbatim}
foo :: Int -> Int
foo bar = bar * 10 + 4
\end{verbatim}

使用Haskell编程的主要工作就是定义函数和类型。并且通常他们所需要的时间差不多相等。Haskell拥有许多种不同的定义函数的方法，上面的内容并没有充分地介绍它们。稍后，一旦我们拥有一些方法后，我们会继续讨论它们。

\section{Part IV: Haskell的列表和列表推导}

熟悉C语言的读者很清楚指针是C语言中的主要对象，且对于几乎所有命令式语言来说，最有用的结构是数组，一个随机访问序列，其值（通常）有序地存储在内存中。Haskell也有数组，不过Haskell中最常用的对象是列表。Haskell中的列表只能从开始处进行访问，且其值在内存中也不是有序存储的。这听起来很糟糕，不过Haskell的这个古怪的能力却使得使用列表比数组更自然，且事实上也更快。让我们从一段从0开始计算Fibonacci数的C代码开始吧：

\begin{verbatim}
int fib (int n) {
    int a = 0, b = 1, i, temp;
    for (i = 0; i < n; i++) {
        temp = a + b;
        a = b;
        b = temp;
    }
    return a;
}
\end{verbatim}

对于计算一个特定的值来说，这样的实现还说得过去，不过当你想要创建一个Fibonacci序列的话，事情将变得很丑陋：

\begin{verbatim}
int * fibArray(int n) {
    int * fibs;
    fibs = (int *)malloc((sizeof int) * n);
    for (i = 0; i < n; i++) {
        fibs[i] = a;
        temp = a + b;
        a = b;
        b = temp;
    }
    return fibs;
}
\end{verbatim}

我所说的'变得丑陋'的意思是在上述函数中包含了一些本不应该包含的东西：列表的大小。Fibonacci序列是无限的，但上面的代码并没有表示出来，而只是表示了其中的一个部分。这听起来也并不那么糟糕，除非你一开始就不知道你需要多少个值。

在Haskell中，用于计算单个fibonacci值的函数'fib'可以这样来实现：

\begin{verbatim}
fib :: Int -> Int
fib n = fibGen 0 1 n
    
fibGen :: Int -> Int -> Int -> Int
fibGen a b n = case n of
    0 -> a
    n -> fibGen b (a + b) (n - 1)
\end{verbatim}

这是在C代码的基础上做的一个微小的改进，但是变动不大。注意函数fibGen的类型是“Int to Int ot Int to Int”，意思是它接受三个Int参数，并且返回一个Int类型的结果。后面会有更多说明。还要注意这里使用了一个递归函数。在Haskell中递归无处不在。你的大多数带有'循环'的函数将使用包含递归的实现替代，除非你使用下面某一种更为复杂的方法。别担心，GHC编译器会优化Haskell代码的。我曾使用Haskell写过一个Pascal编译器，它运行起来比我的同事用C写的所有代码都要快，而且是正确的。

与C相比真正的改进见下面的定义序列：

\begin{verbatim}
fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
\end{verbatim}

别害怕。一旦你理解了这个函数，你将会理解至少一半的Haskell的核心内容(the intracies of Haskell)。让我们自顶向下理解。在Haskell中，列表写成：

\begin{verbatim}
[ 4, 2, 6, 7, 2 ]
\end{verbatim}

This is the list of 4, then 2, then 6, etc. ':'操作符用于将一个值放在列表前面(左边)。例如，

\begin{verbatim}
temp = 1 : [ 4, 2, 5 ]
\end{verbatim}

是列表[ 1, 4, 2, 5 ]。

这样一来，上面代码中的fibs是一个整型数列表，它的前两个值为0和1。到目前为止一直都很好，至少'fibs'的前两个值没有问题，if the thing even compiles。接下来的部分看起来真的十分怪异，然而它却是将要派上用场的一种Haskell方法，称为'列表推导'。你可以直接定义右值，而无须先分配空间，再用恰当的右值填充。That's my statement from Part II, anyway. 列表推导工作方式类似这样的：

\begin{verbatim}
[ func x | x <- list, boolFunc x ]
\end{verbatim}

在定义中部，有一个'list'，从该列表中取出的值称为x。这些是按序排列的列表中的值。如果'boolFunc x'是真，那么x将被传到新列表中使用。在'fibs'例子中并没有boolFunc函数，这里包含它是因为它用起来也十分方便。'func x'将一些函数应用于x，然后其结果排在最终结果列表的后面，假设'boolFunc x'为真，这里是一个列表及其在列表推导中使用的例子：

\begin{verbatim}
nums :: [Int]
nums = [ 4, 2, 6, 8, 5, 11 ]

[ x + 1 | x <- nums ]
= [ 5, 3, 7, 9, 6, 12 ]

[ x * x | x <- nums, x < 7 ]
= [ 16, 4, 36, 25 ]

[ 2 * x | x <- 9 : 1 : nums ]
= [ 18, 2, 8, 4, 12, 16, 10, 22 ]

[ "String" | x <- nums, x < 5 ]
= [ "String", "String" ]
\end{verbatim}

注意，列表元素的次序是保持不变的，这在我们的例子中非常重要。同时，列表推导后的列表元素类型不必是nums列表的类型，x实际上也不必用在函数中。让我们回到'fibs'吧。

\section{Part V: 了解'fibs'的意义以及为何惰性求值这么重要}

我们继续考虑Fibonacci数列表的定义，这里再列出一下例子代码：

\begin{verbatim}
fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
\end{verbatim}

'(a, b)'和'zip fibs (tail fibs)'和所有那些都是什么？好吧，Haskell拥有一个比其它语言更具表现力的类型系统。和在Python中一样，'(a, b)'是一个元组(tuple)，即将两个值粘在一起。这是一种方面的存储和传递多个值的方法，比struct更方便。只是通过加上括号和足够的逗号，你就可以按你的喜好传递一组值。唯一的把戏是Haskell要求你保持一致，这句的意思是选择正确的类型。很清楚，'(a, b)'是'(Int, Int)'类型。或：

\begin{verbatim}
(a, b) :: (Int, Int)
\end{verbatim}

因此'zip fibs (tail fibs)'的类型为'[(Int, Int)]'，说成是两个Int型组成二元组列表可能更为清晰。

\begin{verbatim}
zip fibs (tail fibs) :: [(Int, Int)]
\end{verbatim}

在GHCi和Hugs中，针对变量或函数使用':t'命令命令可告知你这些信息。很方便。

'zip'是什么？它的类型和含义在这里给出：

\href{http://www.haskell.org/ghc/docs/latest/html/libraries/base/Prelude.html#19}{Prelude, Section: Zipping and Unzipping Lists}

'zip'，顾名思义，接受两个列表，并把它们像拉链一般的铰合在一起，返回一个元组的列表。每个元组的左边元素来自第一个列表，同样右边元素来自第二个列表。

\begin{verbatim}
zip [ 1, 3, 6, 2 ] [ "duck", "duck", "duck", "goose" ]
= [ (1, "duck"), (3, "duck"), (6, "duck"), (2, "goose") 
\end{verbatim}

'(tail fibs)'是什么？'tail'是一个十分容易理解的函数：它移除一个列表的第一个元素并返回其余元素组成的列表。这个语句可能有轻微的误导，使用'tail'不会改变'fibs'列表，我们之前说过，Haskell没有更新操作。实际上是'tail'只是计算出合适的结果并返回，而不是更改'fibs'。

\begin{verbatim}
tail [ 10, 20, 30, 40, 50 ]
= [ 20, 30, 40, 50 ]
\end{verbatim}

看上去'zip fibs (tail fibs)'可能有了正确的类型，但是这个类型是什么呢？

\begin{verbatim}
fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
\end{verbatim}

zip的第一个参数是'fibs'，即这组表达式定义的那个列表！怎么搞的？你可以这么做吗？是的，你可以。我们来看，'fibs'是整个列表，包含开始的两个列表元素0和1。所以zip函数创建的列表中的头两个元组的左边元素分别是0和1。那'zip fibs (tail fibs)'是什么呢？喔，第一个元素的值肯定是(0, 1)。为什么呢？因为fibs列表的第一个元素是0，且(tail fibs)结果列表的第一个元素是1，即fibs列表的第二个元素。那么zip fibs (tail fibs)的第二个值是多少呢？是(1, 1)。右侧的1是从哪里来的呢？它是fibs结果列表的第三个元素值，即我们刚刚计算出的那个。zip fibs (tail fibs)的第一个值是(0, 1)，即列表推导中的'(a, b)'。所以在这个推导中的第一个值是0 + 1，或者1。它也是fibs列表中的第三个值。依次类推。

你是否已经领会所有这些了呢？fibs的定义是在计算自己的同时对自己求值。那么为何没有因未定义的值而导致的某种错误发生呢？这一切的奥秘就是Haskell的惰性(laziness)。求值总是计算过程之后的一步，所以求值总是在需要下一个计算的时刻被处理。最后，这个列表是无穷的。当然了，没有计算机可以存储无穷多的数据。那么它究竟占用多少空间？答案很简单：直到你从fibs列表中读到某些值并打印它们，那只有0和1，外加生成列表其余部分的函数。当你读了一些之后，fibs将求值到那个点，不会继续向后求值。fibs是全局定义，所以其定义将常驻内存，可以保证后续值读取得非常快捷。用Hugs或GHCi试试下面的例子代码，你就会知道我的意思了。

\begin{verbatim}
fibs !! 2
fibs !! 4
fibs !! 30
fibs !! 30
fibs !! 6
fibs !! 20
fibs !! 30
take 10 fibs
\end{verbatim}

'!!'是Haskell中列表的'index'操作符。它遍历列表并返回第n个元素，该index从0开始，与C/C++和Python中用法相同。'take 10 fibs'将返回fibs列表的前10个值。注意，fibs列表长度是无穷的。如果你只是敲入'fibs'，输出将永远继续下去。

为什么列表只是在你要打印它的时候需要求值呢？Haskell使用了'惰性'方法，意思是如果不需要工作，那么它就不工作。C程序员知道布尔操作符'\&\&'和'||'使用的都是'短路'机制，意思是右端的表达式直到需要时才求值。这为很多巧妙的技巧提供了方便之门，如对NULL指针不解引用等。\textbf{整个Haskell语言都具有这种短路行为，包括你自己实现的函数}。这听起来很奇怪，但当我们深入Haskell的各种方法时，它会变得越来越重要。

这也给我们带来了Haskell的其他古怪特性之一：\textbf{通常定义泛型函数比定义具化的函数更为简单}。这些也是你必须习惯的古怪特性之一，你将很可能再次遇到它，再一次。

拍拍自己的后背。如果你理解了上述内容，或者至少在Hugs中尝试后理解了，那么你就理解了Haskell大约一半常见用法。准备好学习剩下的那一半了吗？也许应该休息一下，再在Hugs或GHCi中作些练习。
